<!--
通用表格对象
包含表格工具栏、分页插件，并提供了多个插槽来解决各种定制需求
最后修改：20220409
作者：ljy
一、用法
（一）基本用法
1.1 引入组件的方式
import Table from '@/components/base/table/Table.vue';
1.2 使用方式
<Table
      :table="table"
      :tableColumns="tableColumns"
      :loadTableList="loadTableList"
      :changePageSize="changePageSize"
      :changePage="changePage"
    >
1.3 参数说明：
1.3.1 table、loadTableList、changePageSize和changePage的详细说明参照文件'@/api/uem/useTableList.ts'，为表格封装的必备元素
1.3.2 tableColumns为表格的列字段集合，用于实现表格的动态显示。
例如：各种类型的解释详见@/types/interface/base_interface的TableColumn接口
const tableColumns = ref<TableColumn[]>([
  {
    label: '角色名称',
    prop: 'nameZh',
    fixed: true,
  },
  {
    label: '角色标识',
    prop: 'name',
    width: 150,
  },
  {
    label: '默认角色',
    prop: 'defaultRole',
    width: 100,
    type: 'custom',
  },
  {
    label: '启用状态',
    prop: 'enabled',
    width: 100,
    type: 'custom',
  },
  {
    label: '创建时间',
    prop: 'createTime',
  },
]);
1.3.2.1 type参数说明：
（1）普通列字段
如果type属性为空，则默认直接显示该字段的值。
字段示例：
 {
    label: '创建时间',
    prop: 'createTime',
  },
（2）在字典中取出键对应的值
要求type属性为dict；并且dict属性要有值传入，且类型为Option[]。
例如字典{1:'男',2,'女'}，账户性别为1则显示为男。
字段示例：
  {
    label: '默认角色',
    prop: 'defaultRole',
    width: 100,
    type: 'dict',
    dict: yesOrNoDict,
  },
（二)表格插槽
说明表格工具栏、表格只包含了基本的功能，实际开发中通常需要满足各种需求，此时可以插槽进行定制开发
1.表格插槽
1.1 定制列插槽
说明：一般用在业务展示要求比较高的指定表格列
1.1.1 例：默认角色
<template #defaultRole="scope">
  <el-tag :type="`${scope.row.defaultRole==true ? 'success' : 'info'}`">{{dictToMap(yesOrNoDict).get(scope.row.defaultRole)}}</el-tag>
</template>
（1）满足条件
首先需要将该字段设置为type: 'custom',如下
[
  ……
  {
    label: '默认角色',
    prop: 'defaultRole',
    width: 100,
    type: 'custom',
  },
  ……
]
（2）<template #defaultRole="scope">的说明
#defaultRole对应列属性defaultRole
scope的值包含两个，分别是row和item属性。其中row属性与element-table中的scope.row属性一致；scope.item属性为字段列的信息。
1.2 定制操作列插槽（一般在右侧）
说明：一般用对表格单行有特殊操作要求的时候
1.1.1 例如：
<template #table_right_operator>
  <el-table-column
    fixed="right"
    label="操作"
    align="center"
    width="330">
    <template #default="scope">
      <el-button  type="primary"  size="small" icon="Edit" plain @click="print(scope.row);">编辑</el-button>
      <el-button  type="primary"  size="small" icon="User" plain>分配权限</el-button>
    </template>
  </el-table-column>
</template>
（1）满足条件
无
（2）<template #defaultRole="scope">的说明
#defaultRole对应列属性defaultRole
scope的值主要包括row属性，其中row属性与element-table中的scope.row属性一致。

2.表格工具栏插槽
2.1 表格左侧工具栏的右侧插槽
说明：在工具栏左侧需要增加操作组件时使用
2.1.1 例：在左侧工具栏右侧插入一个图标
<template #toorbar_left_right>
  <el-button icon="Plus" type="primary" style="margin-left: 0;">分配角色</el-button>
</template>

-->
<template>
  <div>
    <!--表格工具栏-->
    <TableToolbar
      ref="tableOperationRef"
      :query="query"
      :isTreeTable="isTreeTable"
      :tableColumns="tableColumns"
      :loadTableList="loadTableList"
      :showDeleteMultipleSelectionButton="typeof deleteMultipleSelectionUrl ==='string' && deleteMultipleSelectionUrl.length && showTableCheckbox"
      :multipleSelection="multipleSelection"
      :importUrl="importUrl"
      :importButtonText="importButtonText"
      :importExcelSuccessAndCloseDialog="importExcelSuccessAndCloseDialog"
      :downloadTemplateUrl="downloadTemplateUrl"
      :templateFileName="templateFileName"
      :downloadUrl="downloadUrl"
      :dataFileName="dataFileName"
      @tableLadding="changeTableLoading"
      @changeColumnSelect="getColumnsSelected"
      @deleteMultipleSelection="deleteMultipleSelection"
      @addRow="addRow"
      @expandAll="expandAll"
      @shrinkAll="shrinkAll"
    >
      <!--把表格左侧工具栏的右侧插槽继续网上暴露-->
      <template #toorbar_left_right>
        <slot name="toorbar_left_right"></slot>
      </template>
    </TableToolbar>
    <!--表格对象-->
    <el-table ref="tableRef"
              v-if="refreshTable"
              :data="table.data"
              :row-key="rowId"
              table-layout="fix"
              v-loading="tableLoading"
              @selection-change="handleSelectionChange"
              :tree-props="{children: 'children', hasChildren: 'hasChildren'}"
              highlight-current-row
              :default-expand-all="isExpandAll"
              stripe
              border
              max-height="600px">
      <el-table-column
        v-if="showTableCheckbox"
        type="selection"
        width="40"
        align="center"/>
      <el-table-column  v-if="showTableIndex" align="center" type="index"  fixed/>
      <template
        v-for="(item,index) in tableColumnsVisible"
        :key="index+5">
        <el-table-column
          :label="item.label"
          :prop="item.prop"
          :width="item.width"
          :fixed="item.fixed"
          show-overflow-tooltip
          :align="item.align?item.align:'center'"
        >
          <template #default="scope">
            <!--自定义插槽，在父组件自行进行创建-->
            <span v-if="item.type === 'custom'">
              <slot :name="item.prop" v-bind:item="item" v-bind:row="scope.row">
              </slot>
            </span>
            <span v-else-if="item.type === 'dict'">
              {{dictToMap(item.dict).get(scope.row[item.prop])}}
            </span>
            <span v-else-if="item.type === 'icon'">
              <el-icon v-if="scope.row[item.prop]">
                <component
                  :is="scope.row[item.prop]"
                ></component>
              </el-icon>
            </span>
            <!--用div来限制el-image的高度，避免el-image会有部分高度超出——原因未明-->
            <div v-else-if="item.type === 'image'" style="display: block;height:30px;">
              <el-image
                style="height:30px;"
                v-if="scope.row[item.prop]!=null&&scope.row[item.prop]!=''"
                fit="contain"
                :src="scope.row[item.prop]+'?'+Math.random()"
                :preview-teleported="true"
                :preview-src-list="[scope.row[item.prop]]">
                <template #error>
                  <el-icon><Picture/></el-icon>
                </template>
              </el-image>
              <el-image
                v-else
                style="height:30px;"
                fit="contain"
                :src="noPicSrc"></el-image>
            </div>
            <span v-else>{{scope.row[item.prop]}}</span>
          </template>
        </el-table-column>
      </template>
      <!--自定义插槽，操作列-->
      <slot name="table_operator">
        <el-table-column
          fixed="right"
          label="操作"
          align="center"
          :width="operatorColumnWidth">
          <template #default="scope">
            <slot name="table_operator_left" v-bind:row="scope.row"></slot>
            <el-button  type="primary"  size="small" icon="Edit" plain @click="editRow(scope.row)">编辑</el-button>
            <el-popconfirm
              v-if="showDeleteButton && typeof deleteMultipleSelectionUrl ==='string' && deleteMultipleSelectionUrl.length"
              confirm-button-text="确定"
              cancel-button-text="取消"
              icon="InfoFilled"
              icon-color="red"
              title="确定要删除该记录吗？"
              @confirm="remove(scope.row)"
            >
              <template #reference>
                <el-button  type="danger"  size="small" icon="delete" plain>删除</el-button>
              </template>
            </el-popconfirm>
<!--            <el-button  type="danger"  size="small" icon="delete" plain @click="remove(scope.row)">删除</el-button>-->
            <slot name="table_operator_right" v-bind:row="scope.row">
            </slot>
          </template>
        </el-table-column>
      </slot>
    </el-table>
    <!--分页插件-->
    <TablePagination
      v-if="pagination"
      v-bind="table"
      :changePageSize="changePageSize"
      :changePage="changePage"
    />
  </div>
</template>

<script lang="ts" setup>
// 导入表格工具栏插件
import TableToolbar from '@/components/base/table/TableToolbar.vue';
// 导入分页插件
import TablePagination from '@/components/base/table/TablePagination.vue';
import { ElMessage, ElMessageBox } from 'element-plus';
import {
  defineProps, getCurrentInstance, withDefaults, ref,
} from 'vue';
// 导入项目接口
import { TableColumn, TableData } from '@/types/interface/base_interface';
// 导入字典
import { yesOrNoDict, dictToMap } from '@/api/dict';
import { noPicSrc } from '@/api/pic';

const { proxy } = getCurrentInstance(); // 使用proxy代替ctx，因为ctx只在开发环境有效
// 接收父组件传递过来的值
interface Props {// 需要在父组件引入自定义的@/api/uem/useTableList.ts组件，然后将其中的部分属性传过来
  table: TableData, // 表格对象，包含表格数据和分页信息。表格数据实际业务并不需要，偷懒写法
  query?: Record<string, any>, // 查询表单对象，初步设计是在导出表格时根据查询条件导出数据
  isTreeTable?:boolean, // 是否树形表格，如果为树形表格，会增加扩展和折叠两个按钮，默认为不是树形表格
  loadTableList:()=>void, // 当每页数据显示数发生变化时触发的一个函数
  changePageSize:()=>void, // 当每页数据显示数发生变化时触发的一个函数
  changePage:()=>void, // 当页面发生变化时触发的一个函数
  pagination?:boolean, // 是否显示分页插件，默认显示
  tableColumns:TableColumn[], // 表格的列字段集合
  showAddNewButton?:boolean, // 在表格工具栏是否显示新增按钮
  showDeleteMultipleSelectionButton?:boolean, // 在表格工具栏是否显示删除按钮,还要配合是否有删除地址和表格复选框按钮是否存在判断
  showDeleteButton?:boolean, // 在表格的每行记录都增加删除按钮
  downloadTemplateUrl?:string, // 下载模板地址，也决定表格工具栏是否显示下载模板按钮
  templateFileName?:string, // 下载模板的文件名
  downloadUrl?:string, // 下载表格地址，也决定表格工具栏是否显示下载表格按钮
  dataFileName?:string, // 下载数据的文件名
  importUrl?:string, // 导入地址，决定是否显示导入按钮
  importButtonText?:string, // 导入按钮的名称
  importExcelSuccessAndCloseDialog?:boolean, // 上传成功是否自动关闭对话框
  deleteMultipleSelectionUrl?:string, // 批量删除的地址
  showTableCheckbox?:boolean, // 是否在表格左侧显示复选框按钮,也决定工具栏删除按钮是否显示
  showTableIndex?:boolean, // 是否在表格左侧显示序号
  rowId?:string, // 表格行对象的主键属性，需要根据后端接口数据来决定。默认是id
  operatorColumnWidth?:number, // 表格右侧操作了大小
}
const props = withDefaults(defineProps<Props>(), {
  query: () => ({}), // 返回空对象
  isTreeTable: false,
  pagination: true,
  showDeleteMultipleSelectionButton: true,
  showDeleteButton: true,
  showTableCheckbox: true,
  showTableIndex: false,
  rowId: 'id',
  operatorColumnWidth: 300,
  importExcelSuccessAndCloseDialog: true,
});

// 实现表格的刷新状态
const tableLoading = ref(false);// 表格加载状态
const changeTableLoading = (loading:boolean) => {
  tableLoading.value = loading;
};

// 实现表格的动态列显示
const tableColumnsVisible = ref<TableColumn[]>([]); // 定义用于显示的列
// 当列选择发生变化时，表格的显示数据也发生变化
const getColumnsSelected = (columnsSelected:string[]) => {
  tableColumnsVisible.value = [];
  props.tableColumns.forEach((tableColumn:TableColumn) => {
    if (columnsSelected.indexOf(tableColumn.prop) !== -1) {
      tableColumnsVisible.value.push(tableColumn);
    }
  });
};

// 表格复选框
const multipleSelection = ref([]); // 复选框选择的记录row
const handleSelectionChange = (selection:[]) => { // 当选择项发生变化时会触发该事件，这里用于获取选择的记录（多选）.value可以获取所有选择了的行记录row
  multipleSelection.value = selection;
  proxy.$emit('selectionChange', multipleSelection.value);
};

// 单个记录的删除按钮操作
const remove = async (row:any):Promise<void> => {
  if (props.deleteMultipleSelectionUrl) {
    const ids:any[] = [row[props.rowId]]; // 定义要批量删除的主键
    const deleteForm = {
      ids: ids.toString(),
    };
    const resData = await new proxy.$Request(props.deleteMultipleSelectionUrl, null, { params: deleteForm }).delete();
    if (resData.success) {
      props.loadTableList(); // 刷新表格
    }
  }
};

// 点击添加按钮，由父组件来决定实现方法
const addRow = () => {
  proxy.$emit('addRow', null);
};

// 树形表格扩展和收缩功能
const isExpandAll = ref(true); // 是否全部展开
const refreshTable = ref(true); // 重新渲染表格的状态
// 定义表格ref对象
const tableRef = ref();
// 定义函数，遍历树形表格所有节点，进行扩展或收缩。
const toggleAllRowExpansion = (array:any[], expand:boolean) => { // expand表示是否扩展
  // 递归算法在树形多的时候有些差，这里进行修改
  // array.forEach((row:any) => {
  //   tableRef.value.toggleRowExpansion(row, expand); // 全部折叠
  //   if (row.children && row.children.length > 0) {
  //     toggleAllRowExpansion(row.children, expand);
  //   }
  // });
  // 改进递归算法
  let childrens:any[] = []; // 获取本级所有子节点后再进行递归
  array.forEach((row:any) => {
    tableRef.value.toggleRowExpansion(row, expand); // 全部折叠
    if (row.children && row.children.length > 0) {
      row.children.forEach((rowChildren:any) => { // 遍历子节点，主要要检查该子节点下是否还有节点，减少递归次数
        if (rowChildren.children && rowChildren.children.length > 0) { // 如果子节点下还有节点，则需要遍历该节点
          childrens.push(rowChildren);
        }
      });
    }
  });
  if (childrens.length > 0) {
    toggleAllRowExpansion(childrens, expand);
  }
};
// 点击扩展全部按钮
const expandAll = () => {
  toggleAllRowExpansion(props.table.data, true);
};
// 点击收缩全部按钮，用于树形表格
const shrinkAll = async () => {
  // 方法一：传统方法折叠。在节点多了之后，使用下述收缩速度会很慢，但并非性能原因，如果将浏览器变小只看到一两个菜单，收缩就还会很快。
  // 经过检查，速度慢的原因主要是表格行和列过多的原因导致。所以要控制列显示的数量
  toggleAllRowExpansion(props.table.data, false);
  // 方法二：下面是一个走捷径的把那法，让菜单默认是收缩的，通过v-if来重新渲染表格，达到快速收缩的效果。
  // refreshTable.value = false;
  // isExpandAll.value = false; // 让菜单默认折叠再重新渲染
  // await nextTick();
  // refreshTable.value = true;
};

// 编辑按钮操作
const editRow = (row:any):void => {
  proxy.$emit('editRow', row);
};

// 多个删除操作
const deleteMultipleSelection = async ():Promise<void> => {
  if (multipleSelection.value.length > 0) {
    ElMessageBox.confirm(
      `确定要删除这${multipleSelection.value.length}个记录吗？`,
      '警告',
      {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning',
      },
    ).then(async () => {
      const ids:any[] = []; // 定义要批量删除的主键
      for (let i = 0; i < multipleSelection.value.length; i += 1) {
        const item = multipleSelection.value[i];
        ids.push(item[props.rowId]);
      }
      const deleteForm = {
        ids: ids.toString(),
      };
      if (props.deleteMultipleSelectionUrl) {
        const resData = await new proxy.$Request(props.deleteMultipleSelectionUrl, null, { params: deleteForm }).delete();
        if (resData.success) {
          props.loadTableList(); // 刷新表格
        }
      }
    }).catch(() => {
      // 点击了取消按钮
    });
  } else {
    ElMessage.error('请至少选择一条数据');
  }
};
</script>
<style lang="scss" scoped>

</style>
